//
//  RemoteOutput.m
//  RemoteIO
//
//  Created by Norihisa Nagano
//
//  copied from https://github.com/reinforce-lab/MonoTouch.AudioUnit
//
//  modified by chris allert to use libpatches for aftertaste
//

#import "RemoteOutput.h"

#import "libpatches.h"
#import "truerand.h"
#import <stdlib.h>
#import <unistd.h>

@implementation RemoteOutput

@synthesize sampleRate;

static OSStatus renderCallback(void*                       inRefCon,
                               AudioUnitRenderActionFlags* ioActionFlags,
                               const AudioTimeStamp*       inTimeStamp,
                               UInt32                      inBusNumber,
                               UInt32                      inNumberFrames,
                               AudioBufferList*            ioData){
    //[1]RemoteOutputのインスタンスにキャストする
    // RemoteOutput* def = (RemoteOutput*)inRefCon;    
    //サイン波の計算に使う数値の用意
    // float freq = 440 * 2.0 * M_PI / def.sampleRate;
    //phaseはサウンドの再生中に継続して使うため、RemoteOutputのプロパティとする
    // double phase = def.phase;
    //[2]値を書き込むポインタ
    AudioUnitSampleType *outL = ioData->mBuffers[0].mData;
    AudioUnitSampleType *outR = ioData->mBuffers[1].mData;
    
    for (int i = 0; i< inNumberFrames; i++){
        //[3]サイン波を計算
        // float wave = sin(phase);
        //[4] 8.24固定小数点に変換
        // AudioUnitSampleType sample = wave * (1 << kAudioUnitSampleFractionBits);
        // *outL++ = sample;
        // *outR++ = sample;
        // phase = phase + freq;
	process(1);
	extern AudioUnitSampleType * output_samples;
	extern int output_channels;
	*outL++ = output_channels ? output_samples[0] : 0;
	*outR++ = output_channels ? output_samples[1 % output_channels] : 0;
    }
    //def.phase = phase;
    return noErr;
}

- (id)init{
    self = [super init];
    if (self != nil)[self prepareAudioUnit];
    /* setup libpatches */ 
    [self initPatches];
    return self;
}

-(BOOL)isPlaying{
    return isPlaying;
}

static void myInterruptionListenerCallback (
	void     *inClientData,
	UInt32   inInterruptionState
) { };

void myAudioRouteChangeListenerCallback (
	void                      *inClientData,
	AudioSessionPropertyID    inID,
	UInt32                    inDataSize,
	const void                *inData
) { }

- (void)prepareAudioUnit{    
	// from Technical Q&A QA1626 for background audio
	AudioSessionInitialize(
			NULL, 
			kCFRunLoopDefaultMode, 
			myInterruptionListenerCallback, 
			self
	);
	AudioSessionAddPropertyListener(
		kAudioSessionProperty_AudioRouteChange,
				                                
		myAudioRouteChangeListenerCallback, 
		self
	);
	UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
	AudioSessionSetProperty(
			kAudioSessionProperty_AudioCategory, 
			sizeof(sessionCategory), 
			&sessionCategory
	);
    //RemoteIO Audio UnitのAudioComponentDescriptionを作成
    AudioComponentDescription cd;
    cd.componentType = kAudioUnitType_Output;
    cd.componentSubType = kAudioUnitSubType_RemoteIO;
    cd.componentManufacturer = kAudioUnitManufacturer_Apple;
    cd.componentFlags = 0;
    cd.componentFlagsMask = 0;
    
    //AudioComponentDescriptionからAudioComponentを取得
    AudioComponent component = AudioComponentFindNext(NULL, &cd);
    
    //AudioComponentとAudioUnitのアドレスを渡してAudioUnitを取得
    AudioComponentInstanceNew(component, &audioUnit);
    
    //AudioUnitを初期化
    AudioUnitInitialize(audioUnit);
    
    AURenderCallbackStruct callbackStruct;
    callbackStruct.inputProc = renderCallback;//コールバック関数の設定
    callbackStruct.inputProcRefCon = self;//コールバック関数内で参照するデータのポインタ
    
    AudioUnitSetProperty(audioUnit, 
                         kAudioUnitProperty_SetRenderCallback, 
                         //サイン波の値はAudioUnitに入ってくるものなのでScopeはInput
                         kAudioUnitScope_Input,
                         0,   // 0 == スピーカー
                         &callbackStruct,
                         sizeof(AURenderCallbackStruct));
    sampleRate = 44100.0;
    
    AudioStreamBasicDescription audioFormat;
    audioFormat.mSampleRate         = sampleRate;
    audioFormat.mFormatID           = kAudioFormatLinearPCM;
    audioFormat.mFormatFlags        = kAudioFormatFlagsAudioUnitCanonical;
    audioFormat.mChannelsPerFrame   = 2;
    audioFormat.mBytesPerPacket     = sizeof(AudioUnitSampleType);
    audioFormat.mBytesPerFrame      = sizeof(AudioUnitSampleType);
    audioFormat.mFramesPerPacket    = 1;
    audioFormat.mBitsPerChannel     = 8 * sizeof(AudioUnitSampleType);
    audioFormat.mReserved           = 0;
    
    AudioUnitSetProperty(audioUnit,
                         kAudioUnitProperty_StreamFormat,
                         kAudioUnitScope_Input,
                         0,
                         &audioFormat,
                         sizeof(audioFormat));
}

-(void)play{
    if(!isPlaying) {
	    AudioSessionSetActive(true);
	    AudioOutputUnitStart(audioUnit);
    }
    isPlaying = YES;
}

-(void)pause{
    if(isPlaying)AudioOutputUnitStop(audioUnit);
    isPlaying = NO;
}

-(void)stop{
	[self pause];
	[self initPatches];
}

- (void)dealloc{
    if(isPlaying)AudioOutputUnitStop(audioUnit);
    AudioUnitUninitialize(audioUnit);
    AudioComponentInstanceDispose(audioUnit);
    [super dealloc];
}

#pragma mark -
#pragma mark Interface to libpatches

- (void)parsePatchLine: (NSString*)s {
	NSArray * words = [
		[[s stringByReplacingOccurrencesOfString: @":"
			withString: @" : "] 
			componentsSeparatedByCharactersInSet:
			[NSCharacterSet whitespaceCharacterSet] ]
				filteredArrayUsingPredicate:
					[NSPredicate predicateWithFormat:
						@"SELF != ''"]
	];
	if (nil == words) {
		NSLog(@"no words");
		return;
	}

	// module <type> <name>
	if (
		[[words objectAtIndex: 0] isEqualToString:@"module"] 
		&& [words count] > 2
	) {
		[self declareModule: [words objectAtIndex: 1] 
			withName: [words objectAtIndex: 2]
		];
		return;
	};

	// init <module> <param> <value>
	if (
		[[words objectAtIndex: 0] isEqualToString:@"init"] 
		&& [words count] > 3
	) {
		[self setupModule: [words objectAtIndex: 1] 
			withParam: [words objectAtIndex: 2]
			toValue: [words objectAtIndex: 3]
		];
		return;
	};

	// patch <to>:<input> <from>:<output>
	if (
		[[words objectAtIndex: 0] isEqualToString:@"patch"] 
		&& [words count] > 6
		&& [[words objectAtIndex: 2] isEqualToString:@":"]
		&& [[words objectAtIndex: 5] isEqualToString:@":"]
	) {
		[self patchModule: [words objectAtIndex: 1] 
			withInput: [words objectAtIndex: 3]
			fromModule: [words objectAtIndex: 4]
			withOutput: [words objectAtIndex: 6]
		];
		return;
	}

	// patch <to>:<input> <constant>
	if (
		[[words objectAtIndex: 0] isEqualToString:@"patch"] 
		&& [words count] > 4
		&& [[words objectAtIndex: 2] isEqualToString:@":"]
	) {
		[self patchModule: [words objectAtIndex: 1] 
			withInput: [words objectAtIndex: 3]
			fromConstant: [words objectAtIndex: 4]
		];
		return;
	}

}

- (void)initPatches {
	srand(time(0) + getpid());

	startup();

}


- (void)declareModule: (NSString*)m withName: (NSString*)k {
	safe_module((char *)[m UTF8String], (char *)[k UTF8String]);
}

- (void)patchModule: (NSString*)m withInput: (NSString*)i fromConstant: (NSString*)v {
	safe_patch_constant((char *)[m UTF8String], (char *)[i UTF8String], (char *)[v UTF8String]);
}

- (void)patchModule: (NSString*)m withInput: (NSString*)i fromModule: (NSString*)n withOutput: (NSString*)o {
	safe_patch((char *)[m UTF8String], (char *)[i UTF8String], (char *)[n UTF8String], (char *)[o UTF8String]);
}

- (void)setupModule: (NSString*)m withParam: (NSString*)k toValue: (NSString*)v {
	safe_init((char *)[m UTF8String], (char *)[k UTF8String], (char *)[v UTF8String]);
}

@end
